package distributedbank;

import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

import distributedbank.server.messages.OracleKill;
import distributedbank.server.messages.OracleNotification;

/**
 * Oracle is the failure detector. It keeps one list of servers for each bank
 * branch. It can destroy a server or recover a destroyed server. It notify all
 * servers when some server is bad.
 * 
 * 
 * NOW IN PHASE 4, ORACLE is no longer ORACLE. The FDService has substituted the
 * ORACLE. All the messages ORACLE send out are changed to be sending out by
 * FDVoter.
 * 
 * 
 * 
 */
public class Oracle implements ActionListener {
	private static int width = 600;

	private static int height = 300;

	private final List<Set<Integer>> serverStatus = new ArrayList<Set<Integer>>();

	private final JFrame frame;

	private final JLabel branch1Label;

	private final JLabel branch2Label;

	private final JLabel branch3Label;

	private final JLabel branch4Label;

	private final JLabel branchServerLabel[][] = new JLabel[4][3];

	// enable/disable, branch, servers
	private final JButton serverControl[][][] = new JButton[2][4][3];

	// a disabled counter for each branch, cannot exceed 2
	private final int disabledCounter[] = new int[4];

	private final int disabledServer[] = new int[4];

	private final int maxServerId[] = new int[] { 3, 3, 3, 3 };

	private final JPanel branch1Panel;

	private final JPanel branch2Panel;

	private final JPanel branch3Panel;

	private final JPanel branch4Panel;

	private final JPanel branch1ServerPanel;

	private final JPanel branch2ServerPanel;

	private final JPanel branch3ServerPanel;

	private final JPanel branch4ServerPanel;

	@SuppressWarnings("serial")
	public Oracle() {
		for (int i = 0; i < 4; i++) {
			serverStatus.add(new TreeSet<Integer>());
			for (int j = 0; j < 3; j++) {
				serverStatus.get(i).add(j + 1);
			}
		}

		frame = new JFrame("ORACLE");
		branch1Label = new JLabel("Branch 1");
		branch2Label = new JLabel("Branch 2");
		branch3Label = new JLabel("Branch 3");
		branch4Label = new JLabel("Branch 4");
		branch1Label.setHorizontalAlignment(JLabel.CENTER);
		branch2Label.setHorizontalAlignment(JLabel.CENTER);
		branch3Label.setHorizontalAlignment(JLabel.CENTER);
		branch4Label.setHorizontalAlignment(JLabel.CENTER);

		for (int i = 0; i < 4; i++) {
			for (int j = 0; j < 3; j++) {
				branchServerLabel[i][j] = new JLabel("Server " + (j + 1));
				branchServerLabel[i][j].setHorizontalAlignment(JLabel.CENTER);
			}
		}

		// 0 for disabled, 1 for enabled buttons
		for (int i = 0; i < 2; i++) {
			for (int j = 0; j < 4; j++) {
				for (int k = 0; k < 3; k++) {
					final int branchId = j;
					final int serverId = k;
					if (i == 0) {
						serverControl[i][j][k] = new JButton(
								new AbstractAction("Disable") {
									public final void actionPerformed(
											ActionEvent e) {
										disablePressed(branchId, serverId);
									}
								});
						serverControl[i][j][k].setText("Disable");
					} else {
						serverControl[i][j][k] = new JButton(
								new AbstractAction("Enable") {
									public final void actionPerformed(
											ActionEvent e) {
										enablePressed(branchId, serverId);
									}
								});
						serverControl[i][j][k].setText("Enable");
						// initially, all servers are enabled, set those buttons
						// for false
						serverControl[i][j][k].setEnabled(false);
					}
				}
			}
		}

		branch1Panel = new JPanel();
		branch2Panel = new JPanel();
		branch3Panel = new JPanel();
		branch4Panel = new JPanel();

		branch1ServerPanel = new JPanel();
		branch2ServerPanel = new JPanel();
		branch3ServerPanel = new JPanel();
		branch4ServerPanel = new JPanel();

		frame.setSize(width, height);
		frame.setVisible(true);
		frame.setResizable(false);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setLayout(new GridLayout(2, 2));
		frame.add(branch1Panel);
		frame.add(branch2Panel);
		frame.add(branch3Panel);
		frame.add(branch4Panel);

		branch1Panel.setLayout(new BorderLayout());
		branch1Panel.add("North", branch1Label);
		branch1Panel.add(branch1ServerPanel);
		branch1ServerPanel.setLayout(new GridLayout(3, 3));

		branch1ServerPanel.add(branchServerLabel[0][0]);
		branch1ServerPanel.add(serverControl[1][0][0]);
		branch1ServerPanel.add(serverControl[0][0][0]);
		branch1ServerPanel.add(branchServerLabel[0][1]);
		branch1ServerPanel.add(serverControl[1][0][1]);
		branch1ServerPanel.add(serverControl[0][0][1]);
		branch1ServerPanel.add(branchServerLabel[0][2]);
		branch1ServerPanel.add(serverControl[1][0][2]);
		branch1ServerPanel.add(serverControl[0][0][2]);

		branch2Panel.setLayout(new BorderLayout());
		branch2Panel.add("North", branch2Label);
		branch2Panel.add(branch2ServerPanel);
		branch2ServerPanel.setLayout(new GridLayout(3, 3));
		branch2ServerPanel.add(branchServerLabel[1][0]);
		branch2ServerPanel.add(serverControl[1][1][0]);
		branch2ServerPanel.add(serverControl[0][1][0]);
		branch2ServerPanel.add(branchServerLabel[1][1]);
		branch2ServerPanel.add(serverControl[1][1][1]);
		branch2ServerPanel.add(serverControl[0][1][1]);
		branch2ServerPanel.add(branchServerLabel[1][2]);
		branch2ServerPanel.add(serverControl[1][1][2]);
		branch2ServerPanel.add(serverControl[0][1][2]);

		branch3Panel.setLayout(new BorderLayout());
		branch3Panel.add("North", branch3Label);
		branch3Panel.add(branch3ServerPanel);
		branch3ServerPanel.setLayout(new GridLayout(3, 3));
		branch3ServerPanel.add(branchServerLabel[2][0]);
		branch3ServerPanel.add(serverControl[1][2][0]);
		branch3ServerPanel.add(serverControl[0][2][0]);
		branch3ServerPanel.add(branchServerLabel[2][1]);
		branch3ServerPanel.add(serverControl[1][2][1]);
		branch3ServerPanel.add(serverControl[0][2][1]);
		branch3ServerPanel.add(branchServerLabel[2][2]);
		branch3ServerPanel.add(serverControl[1][2][2]);
		branch3ServerPanel.add(serverControl[0][2][2]);

		branch4Panel.setLayout(new BorderLayout());
		branch4Panel.add("North", branch4Label);
		branch4Panel.add(branch4ServerPanel);
		branch4ServerPanel.setLayout(new GridLayout(3, 3));
		branch4ServerPanel.add(branchServerLabel[3][0]);
		branch4ServerPanel.add(serverControl[1][3][0]);
		branch4ServerPanel.add(serverControl[0][3][0]);
		branch4ServerPanel.add(branchServerLabel[3][1]);
		branch4ServerPanel.add(serverControl[1][3][1]);
		branch4ServerPanel.add(serverControl[0][3][1]);
		branch4ServerPanel.add(branchServerLabel[3][2]);
		branch4ServerPanel.add(serverControl[1][3][2]);
		branch4ServerPanel.add(serverControl[0][3][2]);

		frame.show();
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		this.sendOracleBroadcast();
	}

	private void enablePressed(int branchId, int serverId) {
		if (disabledCounter[branchId]-- == 2) {
			serverControl[0][branchId][disabledServer[branchId]]
					.setEnabled(true);
		}
		serverControl[1][branchId][serverId].setEnabled(false);
		serverControl[0][branchId][serverId].setEnabled(true);
		// no need to check, it cannot go below 0

		String label = branchServerLabel[branchId][serverId].getText();
		int realServerId = Integer
				.valueOf(label.split(" ")[label.split(" ").length - 1]);

		serverStatus.get(branchId).add(realServerId);
		this.sendOracleBroadcast();
	}

	private void disablePressed(int branchId, int serverId) {
		serverControl[1][branchId][serverId].setEnabled(true);
		serverControl[0][branchId][serverId].setEnabled(false);

		String label = branchServerLabel[branchId][serverId].getText();
		int realServerId = Integer
				.valueOf(label.split(" ")[label.split(" ").length - 1]);
		maxServerId[branchId]++;
		branchServerLabel[branchId][serverId].setText("Server "
				+ maxServerId[branchId]);

		if (++disabledCounter[branchId] == 2) {
			for (int k = 0; k < 3; k++)
				if (serverControl[0][branchId][k].isEnabled()) {
					disabledServer[branchId] = k;
					serverControl[0][branchId][k].setEnabled(false);
					break;
				}
		}

		serverStatus.get(branchId).remove(realServerId);
		try {
			Wrapper.sendOracleMessage(new OracleKill(), Wrapper.basePort
					+ (branchId + 1) * 10 + realServerId);
		} catch (IOException e) {
			System.out.println("!Oracle Failed to send message to branch: "
					+ (branchId + 1) + " replica " + realServerId);
		}
		this.sendOracleBroadcast();
	}

	private void sendOracleBroadcast() {
		OracleNotification message = new OracleNotification(this.serverStatus);

		for (Integer branch : new int[] { 1, 2, 3, 4 }) {
			for (Integer server : new int[] { 0, 1, 2, 3 }) {
				int realServerId;
				if (server == 0) {
					realServerId = 0;
				} else {
					String label = branchServerLabel[branch - 1][server - 1]
							.getText();
					realServerId = Integer.valueOf(label.split(" ")[label
							.split(" ").length - 1]);
				}
				int port = Wrapper.basePort + branch * 10 + realServerId;
				try {
					Wrapper.sendOracleMessage(message, port);
				} catch (IOException e) {
					System.out
							.println("Oracle failed to send message to branch: "
									+ branch + " replica " + server);
				}
			}
		}
	}

	public static void main(String[] args) {
		new Oracle();
	}

	@Override
	public void actionPerformed(ActionEvent e_) {
		// TODO Auto-generated method stub

	}
}
